/* pepadagvroadmapper.js www.pepada.com
 * free for non-commercial use, but leave this notice.
 * for any other usage, feedback or enquiries please contact  bruce@mcpher.com
 */
//Namespace
var pepada = {};
//These styles must be present in the wrapper html at a minimumum. Additional
//stlyes can be referenced in the style column for each data item
var pepadaCssFrame = 'pepadarmframe';
var pepadaCssDefault = 'pepadarmdefault';
var pepadaCssScale = 'pepadarmscale';
var pepadaCssData = 'pepadarmdata';
var pepadaCssCallout = 'pepadarmcallout';
var pepadaCssDebug = 'pepadarmdebug';
var pepadaCssExpansion = 'pepadarmexpansion';
var pepadaCssSkin = 'pepadarmskin';
var pepadaCssPoint = 'pepadarmpoint';
var pepadaVersion = 'v1.01';

var pepadaScales = {
		weeks : 1,
		months : 2,
		quarters : 3,
		halfyears : 4,
		automatic : 5
};
//maximum number of ticks to permit in a scale
var pepadapmaxTicks = 20;
//stub for all created elements
var pepadastub = 'pepadarm';
//internal enums
var pepadashapeTypes = {
		frame : 1,
		data : 2,
		scale : 3
};
//nice mths
var mths = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep",
             "Oct", "Nov", "Dec" ];
//----pepada.gvRoadMapper
pepada.gvRoadMapper = function(container) {
	// output place in page
	this.containerElement = container;
};
//----pepada.gvRoadMapper.prototype.draw
pepada.gvRoadMapper.prototype.draw = function(data, options) {

	var html = [];
	var needFields = [ 'activate', 'deactivate', 'id', 'target', 'description' ];
	var good = 1;
	// properties
	this.data = data;
	this.options = options;

	this.dataObjects = this.getdataObjects();
	if (!this.dataObjects.length) {
		alert('Failed to get any data');
		return null;
	}

	for ( var i = 0; i < needFields.length; i++) {
		for (k in this.dataObjects[0]) {
			if (k == needFields[i])
				break;
		}
		if (k != needFields[i]) {
			alert('Required data field ' + needFields[i] + ' was missing');
			good = 0;
		}
	}

	if (!good)
		return null;

	// create the structure, one for each data row

	this.root = new pepada.shapeContainer(pepadashapeTypes.frame, this);

	for ( var row = 0; row < this.dataObjects.length; row++) {
		var sc = this.root.addChild(new pepada.shapeContainer(
				pepadashapeTypes.data, this, this.root, this.dataObjects[row]));
	}

	// clean up linkages and fill in imputed data
	if (this.root.impute()) {

		this.root.associate();

		// create the date scale & adjust lefts & tops & widths
		this.root.createScale();
		this.root.imputeChildren();
		this.root.divDimensions();

		this.root.moveAround();
		this.root.barShapeMake(this.root.top);

		// plot everything

		html.push(this.root.plot(''));
		html.push(this.root.pad());
		if (this.options.debug)
			html.push(this.root.debug());
		this.root.containerElement.innerHTML = html.join('');

	}
};
//----pepada.gvRoadMapper.prototype.getdataObjects
//- convert data table to data object pairs
pepada.gvRoadMapper.prototype.getdataObjects = function(data) {

	var headers = [];
	for ( var i = 0; i < this.data.getNumberOfColumns(); i++) {
		headers[i] = this.data.getColumnLabel(i).toString();
	}
	this.headers = headers;

	var dattab = [];
	for ( var i = 0; i < this.data.getNumberOfRows(); ++i) {
		dattab.push( []);
	}

	for ( var i = 0; i < this.data.getNumberOfRows(); i++)
		for ( var j = 0; j < this.data.getNumberOfColumns(); j++) {
			dattab[i][j] = this.data.getValue(i, j);
		}
	this.dataTable = dattab;

	return pepada.getObjects(this.dataTable, pepada
			.normalizeHeaders(this.headers));
};

//-- shapecontainer object
//----pepada.shapeContainer

pepada.shapeContainer = function(st, rm, rt, dob) {

	this.root = rt ? rt : this;
	this.dataObject = dob;
	this.shapeType = st;
	this.isFrame = (this.shapeType == pepadashapeTypes.frame);
	this.isScale = (this.shapeType == pepadashapeTypes.scale);
	this.isData = (this.shapeType == pepadashapeTypes.data);
	this.scaleObjects = null;

	this.parent = null;
	this.children = [];

	if (dob) {
		this.activate = dob.activate;
		this.deactivate = dob.deactivate;
		this.id = pepada.normalizeHeader(dob.id);
		this.description = dob.description;
		this.target = dob.target ? pepada.normalizeHeader(dob.target)
				: this.root.id;
		this.custom = dob.custom;
		this.callout = dob.callout;
	} else if (this.isFrame) {
		this.roadMapper = rm;
		// options and defaults
		this.otickType = rm.options.tickType ? rm.options.tickType
				: 'automatic';
		this.odescription = rm.options.description ? rm.options.description
				: 'generated by pepada RoadMapper';

		this.containerElement = this.roadMapper.containerElement;
		this.id = pepadastub + 'Frame';
		this.top = 0;
		this.left = 0;
		this.width = this.roadMapper.options.width ? this.roadMapper.options.width
				: this.containerElement.width;
		this.description = this.odescription;

	} else {
		this.target = this.root.id;
	}

	if (this.isData) {
		this.root.activate = this.root.activate ? (this.activate ? (this.root.activate < this.activate ? this.root.activate
				: this.activate)
				: this.root.activate)
				: this.activate;
		this.root.deactivate = this.root.deactivate ? (this.deactivate ? (this.root.deactivate > this.deactivate ? this.root.deactivate
				: this.deactivate)
				: this.root.deactivate)
				: this.deactivate;
	}
};
//----pepada.shapeContainer.prototype.find
pepada.shapeContainer.prototype.find = function(id) {
	// find the given id
	var scfound = this.childExists(id);
	for ( var i = 0; i < this.children.length && !scFound; i++) {
		scfound = this.children[i].find(id);
	}
	return (scfound);
};

//----pepada.shapeContainer.prototype.biggestBranch
pepada.shapeContainer.prototype.biggestBranch = function() {
	// find the longest branch

	var ht = this.children.length;

	for ( var i = 0; i < this.children.length; i++) {
		var t = this.children[i].biggestBranch();
		ht = t > ht ? t : ht;
	}

	return ht;
};
//----pepada.shapeContainer.prototype.plot
pepada.shapeContainer.prototype.plot = function(s) {
	// ? getting the biggest family for which this is a parent
	var splot = s + this.shapeDiv();

	for ( var i = 0; i < this.children.length; i++) {
		splot = this.children[i].plot(splot);
	}

	return splot;
};
//----pepada.shapeContainer.prototype.pad
pepada.shapeContainer.prototype.pad = function() {
	// we've plotted something on a absolute positioning basis. this will pad
	// down so that that following text starts at the right place
	return pepada.element('p', 'style="padding-top:' + this.height
			* this.heightScale() + 'px"', '');

};
//----pepada.shapeContainer.prototype.barExpansion
pepada.shapeContainer.prototype.barExpansion = function() {
	// ? expansion options says whether a parent is bigger than its child, if
	// more than one child somewhere in the family it has to be
	return this.isScale ? false : (this.modelExpansion.marginTop > 0 || this.biggestBranch() > 1);
};
//----pepada.shapeContainer.prototype.barGap
pepada.shapeContainer.prototype.barTopStuff = function() {
	return this.model.marginTop + (pepada.isEvilIE() ? 0 : this.model.paddingTop + this.model.borderTop);
};
//----pepada.shapeContainer.prototype.barGap
pepada.shapeContainer.prototype.barBottomStuff = function() {
	return this.model.marginBottom + (pepada.isEvilIE() ? 0 : this.model.paddingBottom + this.model.borderBottom);
};
//----pepada.shapeContainer.prototype.barGap
pepada.shapeContainer.prototype.barLeftStuff = function() {
	return this.model.marginLeft + (pepada.isEvilIE() ? 0 : this.model.paddingLeft + this.model.borderLeft);
};
//----pepada.shapeContainer.prototype.barGap
pepada.shapeContainer.prototype.barRightStuff = function() {
	return this.model.marginRight + (pepada.isEvilIE() ? 0 : this.model.paddingRight + this.model.borderRight);
};
pepada.shapeContainer.prototype.barWidth = function() {
	// this returns the width of the bar, not including the margins.. which
	// would be the 'visible width' of the bar
	return this.width + this.barRightStuff() + this.barLeftStuff();
};
//----pepada.shapeContainer.prototype.barGap
pepada.shapeContainer.prototype.barStuff = function() {
	// ? this is the addition to the height of a bar at the bottom + top

	return this.barTopStuff() + this.barBottomStuff();
};
//----pepada.shapeContainer.prototype.barGap
pepada.shapeContainer.prototype.barHeight = function() {
	// ? everything about the bar

	return this.barStuff() + this.model.height;

};
//----pepada.shapeContainer.prototype.barGapBeforeChildren
pepada.shapeContainer.prototype.barGapBeforeChildren = function() {
	// ? the gap to insert before my children
	return this.barExpansion() ? this.modelExpansion.paddingTop : 0;
};
//----pepada.shapeContainer.prototype.barGapAfterChildren
pepada.shapeContainer.prototype.barGapAfterChildren = function() {
	// ? the gap to insert after my children
	return this.barExpansion() ? this.modelExpansion.paddingBottom : 0;
};
//----pepada.shapeContainer.prototype.barSpace
pepada.shapeContainer.prototype.barSpace = function() {
	// ? how much space in total i need
	var ht = 0;
	if (!this.children.length) {
		ht = this.barHeight();
	} else {
		if (this.barExpansion()) {
			ht += this.barGapBeforeChildren() + this.barGapAfterChildren();
			for ( var i = 0; i < this.children.length; i++) {
				ht += this.children[i].barSpace();

			}
		} else {
			ht = this.barHeight();

		}
	}

	return ht;
};

//----pepada.shapeContainer.prototype.barShapeHeight
pepada.shapeContainer.prototype.barShapeHeight = function() {
	// ? how big my shape needs to be

	return this.barSpace() - this.barStuff();
};
//----pepada.shapeContainer.prototype.heightScale
pepada.shapeContainer.prototype.heightScale = function() {
	// ? if the given height is non zero, then we need to scale to the correct
	// height
	// the option height takes priority over the computed style
	if (this.root.roadMapper.options.height)
		return this.root.roadMapper.options.height / this.root.height;
	else
		return this.root.model.height ? this.root.model.height
				/ this.root.height : 1;
};
//----pepada.shapeContainer.prototype.barMake
pepada.shapeContainer.prototype.barShapeMake = function(xTop) {
	// ? this is about setting the tops and heights of each shape
	this.top = xTop;
	this.height = this.barShapeHeight();
	var xNextTop = this.top;

	if (this.barExpansion())
		xNextTop += this.barGapBeforeChildren();
	for ( var i = 0; i < this.children.length; i++) {
		this.children[i].barShapeMake(xNextTop);
		xNextTop += this.children[i].barSpace();
	}
};

//----pepada.shapeContainer.prototype.moveAround
pepada.shapeContainer.prototype.moveAround = function() {
	/*
	 * top and height are done elsewhere
	 */
	var w=0;
	if (!this.isFrame) {
		w = this.root.width / (this.root.scaleFinish() - this.root.scaleStart());
		this.width = (this.finish - this.start) * w;
		if (!this.isScale) this.width -= (this.barRightStuff() + this.barLeftStuff()); 
		this.left = this.root.left + this.root.barLeftStuff() + ((this.start - this.root.scaleStart()) * w);

	} else {
		// this needs to adjust the frame content width to account for
		// borders,padding
		this.width -= (this.barLeftStuff() + this.barRightStuff());

	}
	for ( var i = 0; i < this.children.length; i++)
		this.children[i].moveAround();

};
//----pepada.shapeContainer.prototype.addChild

pepada.shapeContainer.prototype.addChild = function(sc, atbeginning) {

	if (this.childExists(sc.id)) {
		alert('attempt to add duplicate child ' + sc.id + ' ignored');
	} else {
		if (atbeginning) {
			this.children.push(null);
			for ( var i = this.children.length - 1; i > 0; i--) {
				this.children[i] = this.children[i - 1];
			}
			this.children[0] = sc;
		} else
			this.children.push(sc);
	}
	return sc;
};
//----pepada.shapeContainer.prototype.removeChild

pepada.shapeContainer.prototype.removeChild = function(n) {

	if (pepada.assert(n < this.children.length && n >= 0,
	'trying to delete a child that doesnt exist')) {
		var a = pepada.arrayCopyDrop(this.children, n);
		for ( var i = 0; i < a.length; i++)
			this.children[i] = a[i];
		this.children.pop();
	}
};
//----pepada.shapeContainer.prototype.childExists

pepada.shapeContainer.prototype.childExists = function(id) {

	for ( var i = 0; i < this.children.length; i++)
		if (this.children[i].id == id)
			return (this.children[i]);
	return null;
};
//----pepada.shapeContainer.prototype.duration

pepada.shapeContainer.prototype.duration = function() {
	return pepada.daysDiff(this.start, this.finish);
};
//----pepada.shapeContainer.prototype.shapeDiv

pepada.shapeContainer.prototype.shapeDiv = function() {
	return pepada.span('id="' + pepadastub + 'div' + this.id + '" class="'
			+ this.shapeClass() + '" style="' + this.shapeStyle() + '"',
			this.description + this.calloutDiv());
};

pepada.shapeContainer.prototype.calloutDiv = function() {
	return this.callout ? pepada.div('id="' + pepadastub + 'callout' + this.id
			+ '" class="' + this.calloutClass() + '" style="'
			+ this.calloutStyle() + '"', this.callout)
			+ this.pointDiv() : ' ';
};
pepada.shapeContainer.prototype.pointDiv = function() {
	return this.callout ? pepada.div('id="' + pepadastub + 'point' + this.id
			+ '" class="' + pepadaCssPoint + ' ' + pepadaCssSkin + '"', '')
			: ' ';
};
//----pepada.shapeContainer.prototype.shapeStyle
pepada.shapeContainer.prototype.shapeStyle = function() {

	return this.shapePositionStyle() + this.shapeTopLeft() + 'width:'
	+ this.width.toFixed(2) + 'px;' + 'height:'
	+ (this.height * this.heightScale()).toFixed(2) + 'px;'
	+ 'font-size:'
	+ (this.model.fontSize * this.heightScale()).toFixed(2) + 'px';
};
//----pepada.shapeContainer.prototype.calloutStyle
pepada.shapeContainer.prototype.calloutStyle = function() {
	var nheight = this.modelCallout.height * this.heightScale();
	var ntop = this.modelCallout.top + this.modelCallout.height - nheight;
	return ('height:' + nheight.toFixed(2) + 'px;' + 'top:' + ntop.toFixed(2) + 'px;');

};

//----pepada.shapeContainer.prototype.shapePositionStyle

pepada.shapeContainer.prototype.shapePositionStyle = function() {
	return 'position:absolute;';
};
//----pepada.shapeContainer.prototype.shapeTopLeft
pepada.shapeContainer.prototype.shapeTopLeft = function() {
	return this.isFrame ? '' : 'top:'
		+ (this.top * this.heightScale()).toFixed(2) + 'px;' + 'left:'
		+ this.left.toFixed(2) + 'px;';
};
//----pepada.shapeContainer.prototype.shapeClass

pepada.shapeContainer.prototype.shapeClass = function() {
	return pepadaCssDefault
	+ ' '
	+ (this.isFrame ? pepadaCssFrame : (this.isScale ? pepadaCssScale
			: pepadaCssData)) + ' ' + pepadaCssSkin
			+ (this.custom ? ' ' + this.custom : '');
};
//----pepada.shapeContainer.prototype.calloutClass
pepada.shapeContainer.prototype.calloutClass = function() {
	return pepadaCssDefault + ' ' + pepadaCssCallout + ' ' + pepadaCssSkin
	+ ' ' + (this.custom ? this.custom : '');
};
//----pepada.shapeContainer.prototype.scaleStart

pepada.shapeContainer.prototype.scaleStart = function() {
	return this.scaleObjects[0].start;
};
//----pepada.shapeContainer.prototype.scaleStart

pepada.shapeContainer.prototype.scaleFinish = function() {
	return this.scaleObjects[this.scaleObjects.length - 1].finish;
};
//----pepada.shapeContainer.prototype.impute

pepada.shapeContainer.prototype.impute = function() {

	if (pepada.assert(this.isFrame, ' expected a frame container')
			&& pepada.assert(this.activate && this.deactivate,
			' cant figure out the date range of the roadmap')) {
		this.start = pepada.addDays(this.activate, 0, -1);
		this.finish = pepada.addDays(this.deactivate, 0, 1);

		for ( var i = 0; i < this.children.length; i++) {
			var sc = this.children[i];
			sc.start = sc.activate ? pepada.addDays(sc.activate, 0, -1)
					: this.root.start;
			sc.finish = sc.deactivate ? pepada.addDays(sc.deactivate, 0, 1)
					: this.root.finish;
		}
		return this;
	}
};
pepada.shapeContainer.prototype.imputeChildren = function() {

	this.start = this.activate ? pepada.addDays(this.activate, 0, -1)
			: this.root.scaleStart();
	this.finish = this.deactivate ? pepada.addDays(this.deactivate, 0, -1)
			: this.root.scaleFinish();

	for ( var i = 0; i < this.children.length; i++) {
		this.children[i].imputeChildren();
	}
};
//----pepada.shapeContainer.prototype.divDimensions

pepada.shapeContainer.prototype.divDimensions = function() {
	// here's an interesting problem. apply the styles of this set of objects so
	// I can get the paramters,
	// then delete the object again
	this.getParametersFromDiv();
	this.getExpansionParameters();
	this.getCalloutParameters();
	for ( var i = 0; i < this.children.length; i++) {
		this.children[i].divDimensions();
	}
};

//----pepada.model
pepada.model = function(dp) {
	// get the properties I want

	this.marginTop = pepada.floatStyle(dp, 'marginTop');
	this.marginBottom = pepada.floatStyle(dp, 'marginBottom');
	this.marginLeft = pepada.floatStyle(dp, 'marginLeft');
	this.marginRight = pepada.floatStyle(dp, 'marginRight');

	this.paddingTop = pepada.floatStyle(dp, 'paddingTop');
	this.paddingBottom = pepada.floatStyle(dp, 'paddingBottom');
	this.paddingLeft = pepada.floatStyle(dp, 'paddingLeft');
	this.paddingRight = pepada.floatStyle(dp, 'paddingRight');

	this.borderTop = pepada.floatStyle(dp, 'borderTopWidth');
	this.borderBottom = pepada.floatStyle(dp, 'borderBottomWidth');
	this.borderLeft = pepada.floatStyle(dp, 'borderLeftWidth');
	this.borderRight = pepada.floatStyle(dp, 'borderRightWidth');

	this.width = pepada.floatStyle(dp, 'width');
	this.height = pepada.floatStyle(dp, 'height');

	this.fontSize = pepada.floatStyle(dp, 'fontSize');
	this.top = pepada.floatStyle(dp, 'top');

};
//----pepada.shapeContainer.prototype.getParametersFromDiv

pepada.shapeContainer.prototype.getParametersFromDiv = function() {

	// create a dummy div and get parameters from the applied css styles

	var dp = pepada.createElem(this.root.containerElement, 'div', this
			.shapeClass(), pepadastub + 'temp');
	// get the properties I want
	this.model = new pepada.model(dp);
	this.root.containerElement.removeChild(dp);

	return this;
};
//----pepada.shapeContainer.prototype.getExpansionParameters

pepada.shapeContainer.prototype.getExpansionParameters = function() {

	// create a dummy div and get parameters from the applied css styles

	var dp = pepada.createElem(this.root.containerElement, 'div', this
			.shapeClass()
			+ ' ' + pepadaCssExpansion, pepadastub + 'temp' + this.id);
	// get the properties I want
	this.modelExpansion = new pepada.model(dp);
	this.root.containerElement.removeChild(dp);

	return this;
};
//----pepada.shapeContainer.prototype.getCalloutParameters

pepada.shapeContainer.prototype.getCalloutParameters = function() {

	// create a dummy div and get parameters from the applied css styles

	var dp = pepada.createElem(this.root.containerElement, 'div', this
			.calloutClass(), pepadastub + 'temp' + this.id);
	// get the properties I want
	this.modelCallout = new pepada.model(dp);
	this.root.containerElement.removeChild(dp);

	return this;
};

//----pepada.shapeContainer.prototype.debug
pepada.shapeContainer.prototype.debug = function() {
	// create a place to put it
	var html = [];

	html.push(this.debugBlock('this.', this
			.setDebugModelFields(this.roadMapper.dataObjects[0])));
	;
	html.push(this.debugBlock('this.', this.setDebugFields()));
	html.push(this.debugBlock('this.model.', this
			.setDebugModelFields(this.model)));
	html.push(this.debugBlock('this.modelExpansion.', this
			.setDebugModelFields(this.modelExpansion)));
	html.push(this.debugBlock('this.modelCallout.', this
			.setDebugModelFields(this.modelCallout)));
	return html.join('');
};
//----pepada.shapeContainer.prototype.debugBlock
pepada.shapeContainer.prototype.debugBlock = function(os, debugFields) {
	// create a place to put it
	var html = [];

	html.push('<div ' + this.debugClass() + '>');

	// do the titles and recurse through
	html.push(pepada.element('h4', '', os));
	html.push('<table ' + '>');
	html.push('<tbody ' + '>');
	html.push('<tr ' + '>');
	html.push(pepada.element('th', '', 'id'));

	for ( var i = 0; i < debugFields.length; i++) {
		html.push(pepada.element('th', '', debugFields[i].toString()));
	}
	html.push('</tr>');
	html.push(this.debugRow(os, debugFields));
	html.push('</tbody></table></div>');

	return html.join('');
};
//----pepada.shapeContainer.prototype.debugClass
pepada.shapeContainer.prototype.debugClass = function() {
	return 'class="' + pepadaCssDefault + ' ' + pepadaCssDebug + '"';
};

//----pepada.shapeContainer.prototype.setDebugFields
pepada.shapeContainer.prototype.setDebugFields = function() {
	// set up fields to debug
	debugFields = [];
	debugFields.push('start');
	debugFields.push('finish');
	debugFields.push('height');
	debugFields.push('width');
	debugFields.push('left');
	debugFields.push('top');
	debugFields.push('heightScale');
	return debugFields;
};
//----pepada.shapeContainer.prototype.setDebugFields
pepada.shapeContainer.prototype.setDebugModelFields = function(m) {
	// set up fields to debug
	debugFields = [];
	for ( var k in m)
		debugFields.push(k);
	return debugFields;
};
//----pepada.shapeContainer.prototype.debugRow
pepada.shapeContainer.prototype.debugRow = function(os, debugFields) {
	// create debug row
	var html = [];
	html.push('<tr>');
	html.push(pepada.element('td', '', this.id));

	for ( var i = 0; i < debugFields.length; i++) {
		var r = debugFields[i];
		if (typeof eval(os + r) == 'function') {
			var res = eval(os + r + '()');
		} else {
			var res = eval(os + debugFields[i]);
		}
		html.push(pepada.element('td', '', pepada.niceDate(res)));
	}

	for (i = 0; i < this.children.length; i++) {
		html.push(this.children[i].debugRow(os, debugFields));
	}
	return html.join('');
};
//----pepada.niceDate
pepada.niceDate = function(d) {

	return d ? (d instanceof Date ? d.getDate().toString() + '-'
			+ mths[d.getMonth()] + '-' + pepada.padYear(d)
			: (typeof d == 'number' ? d.toFixed(1) : pepada.tryString(d))) : '';

};
//----pepada.shapeContainer.prototype.associate
pepada.shapeContainer.prototype.associate = function() {

	if (pepada.assert(this.isFrame, " expected a frame container")) {
		for ( var i = 0; i < this.children.length; i++) {
			var scchild = this.children[i];
			if (scchild.target == this.id)
				scchild.parent = this;

			else {
				scchild.parent = this.childExists(scchild.target);

				if (scchild.parent)
					scchild.parent.addChild(scchild);
				else {
					alert("could not find target "
							+ pepada.tryString(scchild.target) + " for id "
							+ pepada.tryString(scchild.id));
					scchild.parent = this;
				}
			}
		}

		// should have all the parents assigned - need to delete from the frame
		// those that have been moved
		for ( var n = this.children.length - 1; n >= 0; n--) {
			scchild = this.children[n];
			if (pepada.assert(scchild.parent,
					"some problem assigning parent for " + scchild.id)) {
				if (scchild.target != this.id)
					this.removeChild(n);
			}
		}
	}

};
//----pepada.shapeContainer.prototype.createScale
pepada.shapeContainer.prototype.createScale = function(s) {
	// add the scale shapes to the list of things to be plotted
	if (pepada.assert(pepadaScales[this.otickType], 'Unknown ticktype')) {
		ticktype = pepadaScales[this.otickType];
		if (ticktype == pepadaScales.automatic) {
			ticktype = this.autoScale();
		}
		if (pepada.assert(!isNaN(ticktype),'failed to get a scale'))this.scaleObjects = this.createTicks(ticktype);
	}
};
//----pepada.shapeContainer.prototype.autoScale
pepada.shapeContainer.prototype.autoScale = function(s) {
	var idealticks = pepadapmaxTicks * .5;
	var tickdiff = pepadapmaxTicks + 1;

	// try to optimize the number of ticks
	for ( var k in pepadaScales) {
		if (k != 'automatic') {
			var oTicks = this.limitofScale(pepadaScales[k], this.start, this.finish);
			if (Math.abs(idealticks - oTicks.ticks) < tickdiff) {
				best = pepadaScales[k];
				tickdiff = Math.abs(idealticks - oTicks.ticks);
			}
		}
	}
	if (pepada.assert(tickdiff <= pepadapmaxTicks,
	'Couldnt find a feasible automatic scale to use for roadmap'))
		return best;
};
//----pepada.shapeContainer.prototype.createTicks
pepada.shapeContainer.prototype.createTicks = function(s) {
	var oTicks = this.limitofScale(s, this.start, this.finish);
	var scaleObjs = [];
	var ticks = 0;
	var xcds = oTicks.start;
	var sp = this;
	while (xcds < oTicks.finish) {
		if (!pepada.assert(++ticks <= pepadapmaxTicks, 'no room to show scale '
				+ s + ' from ' + pepada.niceDate(this.start) + ' to '
				+ pepada.niceDate(this.finish) + ' :choose another scale'))
			return null;
		// get end date of the tick for this start date
		var oSub = this.limitofScale(s, xcds, xcds);
		if (pepada
				.assert(oSub.finish > xcds, ' got confused calculating scale')) {
			// add it to the pile for plotting
			sc = sp.addChild(new pepada.shapeContainer(pepadashapeTypes.scale,
					this.roadMapper, this), true);
			sc.parent = sp;
			sc.target = sp.id;
			// simulate a regular data container
			sc.activate = oSub.start;
			sc.deactivate = oSub.finish;
			sc.start = sc.activate;
			sc.finish = sc.deactivate;
			sc.description = oSub.finishText;
			sc.id = pepadastub + 'Scale' + ticks.toString();
			sp = sc;
			// we'll probably need a convenient record of all the start and end
			// dates later
			scaleObjs.push(oSub);

			// move to the next date along
			xcds = pepada.addDays(oSub.finish, 1);

		} else
			return null;
	}
	return scaleObjs;
};
//----pepada.shapeContainer.prototype.limitofscale
pepada.shapeContainer.prototype.limitofScale = function(s, sd, fd) {

	obj = {};
	obj['start'] = null;
	obj['finish'] = null;
	obj['ticks'] = 0;
	obj['finishText'] = '';
	obj['startText'] = '';

	switch (s) {
	case pepadaScales.weeks:
		obj.start = pepada.addDays(sd, -(sd.getDay() % 7), -1);
		obj.finish = pepada.addDays(fd, 6 - (fd.getDay() % 7), 1);
		obj.ticks = Math
		.floor((1 + pepada.daysDiff(obj.start, obj.finish)) / 7);
		obj.startText = pepada.niceDate(obj.start);
		obj.finishText = pepada.niceDate(obj.finish);
		break;
	case pepadaScales.months:
		obj.start = new Date(sd.getFullYear(), sd.getMonth(), 1);
		obj.finish = pepada.addDays(new Date(fd.getFullYear(),
				fd.getMonth() + 1, 1), -1, 1);
		obj.ticks = Math
		.floor((1 + pepada.daysDiff(obj.start, obj.finish)) / 30);
		obj.startText = mths[obj.start.getMonth()] + '-'
		+ pepada.padYear(obj.start);
		obj.finishText = mths[obj.finish.getMonth()] + '-'
		+ pepada.padYear(obj.finish);
		break;
	case pepadaScales.quarters:
		obj.start = new Date(sd.getFullYear(), sd.getMonth()
				- (sd.getMonth() % 3), 1);
		obj.finish = pepada.addDays(new Date(fd.getFullYear(), fd.getMonth()
				+ 3 - (fd.getMonth() % 3), 1), -1, 1);
		obj.ticks = Math
		.floor((1 + pepada.daysDiff(obj.start, obj.finish)) / 90);
		obj.startText = 'Q'
			+ (1 + Math.floor(obj.start.getMonth() / 3)).toString()
			+ pepada.padYear(obj.start);
		obj.finishText = obj.startText;
		break;
	case pepadaScales.halfyears:
		obj.start = new Date(sd.getFullYear(), sd.getMonth()
				- (sd.getMonth() % 6), 1);
		obj.finish = pepada.addDays(new Date(fd.getFullYear(), fd.getMonth()
				+ 6 - (fd.getMonth() % 6), 1), -1, 1);
		obj.ticks = Math
		.floor((1 + pepada.daysDiff(obj.start, obj.finish)) / 183);
		obj.startText = 'H'
			+ (1 + Math.floor(obj.start.getMonth() / 6)).toString()
			+ pepada.padYear(obj.start);
		obj.finishText = obj.startText;
		break;
	case pepadaScales.years:
		obj.start = new Date(sd.getFullYear(), 0, 1);
		obj.finish = pepada.addDays(
				new Date(obj.start.getFullYear() + 1, 0, 1), -1, 1);
		obj.ticks = Math
		.floor((1 + pepada.daysDiff(obj.start, obj.finish)) / 365);
		obj.startText = obj.start.getFullYear().toString();
		obj.finishText = obj.startText;
		break;
	default:
		pepada.assert(false, 'unknown scale ' + s);
	return null;
	}
	return obj;
};
//----pepada.assert
pepada.assert = function(a, msg) {
	if (!a)
		alert('Assert Failed:' + pepada.tryString(msg));

	return a;
};
//----pepada.tryString
pepada.tryString = function(a) {
	return a ? a.toString() : '';
};
//----pepada.getObjects
pepada.getObjects = function(data, keys) {
	var objects = [];
	for ( var i = 0; i < data.length; ++i) {
		var object = {};
		var hasData = false;
		for ( var j = 0; j < data[i].length; ++j) {
			var cellData = data[i][j];
			if (pepada.isCellEmpty(cellData)) {
				continue;
			}
			object[keys[j]] = cellData;
			hasData = true;

		}
		if (hasData) {
			objects.push(object);
		}
	}
	return objects;
};
//----pepada.isCellEmpty
pepada.isCellEmpty = function(cellData) {
	return typeof (cellData) == "string" && cellData == "";
};
//----pepada.isAlnum
pepada.isAlnum = function(char) {
	return char >= 'A' && char <= 'Z' || char >= 'a' && char <= 'z'
		|| pepada.isDigit(char);
};
//----pepada.isDigit
pepada.isDigit = function(char) {
	return char >= '0' && char <= '9';
};
//----pepada.div
pepada.div = function(s, a) {
	return pepada.element('div', s, a);
};
//----pepada.div
pepada.span = function(s, a) {
	return pepada.element('span', s, a);
};
//----pepada.element
pepada.element = function(e, s, a) {
	return ('<' + e + ' ' + s + '>' + a + '</' + e + '>');
};
//----pepada.normalizeHeaders
pepada.normalizeHeaders = function(headers) {
	var keys = [];
	for ( var i = 0; i < headers.length; ++i) {
		var key = pepada.normalizeHeader(headers[i]);
		if (key.length > 0) {
			keys.push(key);
		}
	}

	return keys;
};
//----pepada.normalizeHeader
pepada.normalizeHeader = function(header) {
	var key = "";
	var upperCase = false;

	for ( var i = 0; i < header.length; ++i) {
		var letter = header.substring(i, i + 1);

		if (letter == " " && key.length > 0) {
			upperCase = true;

			continue;
		}
		if (!pepada.isAlnum(letter)) {

			continue;
		}
		if (key.length == 0 && pepada.isDigit(letter)) {

			continue; // first character must be a letter
		}
		if (upperCase) {
			upperCase = false;
			key += letter.toUpperCase();
		} else {
			key += letter.toLowerCase();
		}
	}

	return key;
};
pepada.addDays = function(d, n, e) {
	var xd = new Date(d);
	xd.setDate(xd.getDate() + n);
	if (e < 0) {
		return new Date(xd.getFullYear(), xd.getMonth(), xd.getDate());

	} else if (e > 0) {
		var nd = new Date(xd.getFullYear(), xd.getMonth(), xd.getDate() + 1);
		return (new Date(nd - 1));

	} else
		return xd;
};

pepada.daysDiff = function(a, b) {
	var oneDay = 1000 * 60 * 60 * 24;
	return Math.floor(b.getTime() / oneDay) - Math.floor(a.getTime() / oneDay);
};
pepada.shortYear = function(d) {
	return d.getYear() % 100;

};
pepada.padYear = function(d) {
	return pepada.padTemplate(pepada.shortYear(d), '00');
};
pepada.arrayCopyDrop = function(a, n) {
	return a.slice(0, n).concat(a.slice(n + 1));
};
pepada.padTemplate = function(n, template) {
	return (template + n.toString()).slice(-template.length);
};
//----pepada.computedStyle
pepada.floatStyle = function(e, s) {
	var value = parseFloat(pepada.computedStyle(e, s));
	if (isNaN(value))
		value = 0;
	return value;
};
//----pepada.computedStyle
pepada.computedStyle = function(e, s) {

	var compute = null;
	var current = null;
	var value = 0;
	try {
		compute = document.defaultView.getComputedStyle(e, null);
		value = compute.getPropertyValue(pepada.propToCss(s));
	} catch (err) {

		try {
			compute = e.currentStyle;
			value = compute.getAttribute(s);
			if (isNaN(parseFloat(value))) // try do deal IE wierdness
			{
				try {
					compute = e.runtimeStyle;
					value = compute.getAttribute(s);
				} catch (err) {

				}
				;
				if (isNaN(parseFloat(value))) {
					switch (s) {
					case 'width':
						value = e.offsetWidth.toString();
						break;
					case 'height':
						value = e.offsetHeight.toString();
						break;
					case 'left':
						value = e.offsetLeft.toString();
						break;
					case 'top':
						value = e.offsetTop.toString();
						break;

					}
				}

			}
			if (value.indexOf('\%') != -1)
				value = (parseFloat(value) / 100).toString() + 'em';
			if (value.indexOf('em') != -1)
				value = (parseFloat(value) / .0625).toString(); // deal with em
			// convert to
			// pixels

		} catch (err) {
			throw ('Unable to get property ' + s + ' cannot run on this browser');
		}
		;
	}
	;

	return value;

};
pepada.propToCss = function(p) {
	// this changes backgroundColor to background=color
	return p.replace(/([A-Z])/g, "-$1").toLowerCase();
};
//----pepada.createElem
pepada.createElem = function(container, ctype, cn, id, text) {
	var dp = document.createElement(ctype ? ctype : 'div');

	if (id)
		dp.id = id;
	if (cn)
		dp.className = cn;
	container.appendChild(dp);
	return dp;
};

pepada.linkStyleSheet = function(sn, en) {
	if (sn) {
		var css = document.createElement('link');
		css.type = 'text/css';
		css.rel = 'stylesheet';
		css.href = sn;
		// various browser approaches
		var uen = en ? en : 'head';
		if (document[uen])
			document[uen].appendChild(css);
		else
			document.getElementsByTagName(uen)[0].appendChild(css);

		return css;
	}
};
pepada.isEvilIE = function() {
	return ((window.XMLHttpRequest == undefined) && (ActiveXObject != undefined));
};
